import { isEmpty, merge, groupBy, map, toPairs, first, find } from "lodash";
import React, { useState, useEffect, useRef, useCallback } from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import Popover from "antd/lib/popover";
import Button from "antd/lib/button";
import Checkbox from "antd/lib/checkbox";
import Select from "antd/lib/select";
import Menu from 'antd/lib/menu';
import Dropdown from 'antd/lib/dropdown';
import Drawer from "antd/lib/drawer";
import DownOutlined from '@ant-design/icons/DownOutlined';
import routeWithUserSession from "@/components/ApplicationArea/routeWithUserSession";
import DynamicComponent from "@/components/DynamicComponent";
import DashboardGrid from "@/components/dashboards/DashboardGrid";
import Parameters from "@/components/Parameters";
import Filters from "@/components/Filters";
import { Row, Col } from 'antd/lib/grid';

import ParticleWave from 'particle-wave';
// import { Fireworks } from 'fireworks-js/dist/react';
import Snowfall from 'react-snowfall';
import animate from 'bubbles-animation';
import DashboardHeader from "./components/DashboardHeader";
import TemplateDrawer from "./components/TemplateDrawer";
import { MenuFoldOutlined } from '@ant-design/icons';

import { Dashboard } from "@/services/dashboard";
import Widget from "@/services/widget";
import recordEvent from "@/services/recordEvent";
import resizeObserver from "@/services/resizeObserver";
import routes from "@/services/routes";
import url from "@/services/url";
import useImmutableCallback from "@/lib/hooks/useImmutableCallback";
import useDashboard from "./hooks/useDashboard";
import { calculateNewWidgetPosition } from "@/services/dashboard"
import location from "@/services/location";
import { policy } from "@/services/policy";
import { axios } from "@/services/axios";
import "./DashboardPage.less";

function DashboardSettings({ dashboardOptions }) {
  const { dashboard, updateDashboard } = dashboardOptions;
  return (
    <div className="m-b-10 p-5 bg-white tiled">
      <Checkbox
        checked={!!dashboard.dashboard_filters_enabled}
        onChange={({ target }) => updateDashboard({ dashboard_filters_enabled: target.checked })}
        data-test="DashboardFiltersCheckbox">
        使用报表级别过滤
      </Checkbox>
    </div>
  );
}

DashboardSettings.propTypes = {
  dashboardOptions: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};

function AddWidgetContainer({ dashboardOptions, className, ...props }) {
  const [isVisible, setIsVisible] = useState(false);
  const [content, setContent] = useState([]);
  const { showAddTextboxDialog, showAddWidgetDialog, showAddContainerDialog, showAddUrlDialog, showAddWeatherDialog, showAddVideoDialog } = dashboardOptions;
  const [currentWidget, setCurrentWidget] = useState(dashboardOptions.dashboard.widgets[0] && first(dashboardOptions.dashboard.widgets, (w) => !!w.template_name));
  const [draggable, setDraggable] = useState({
    status: false,
    dy: 0,
    dx: 0,
  });
  const divRef = useRef();

  const onMouseDown = (setDraggable) => (e) => {
    setDraggable({
      dy: e.clientY - divRef.current.offsetTop,
      status: true,
    });
    divRef.current.style = merge(divRef.current.style, { cursor: 's-resize' });
  }

  const onMouseMove = (draggable) => (e) => {
    if (draggable.status) {
      divRef.current.style.top = `${e.clientY - draggable.dy}px`;
    }
  }

  const onMouseUp = (draggable) => () => {
    setDraggable({
      ...draggable,
      status: false,
    })
   divRef.current.style = merge(divRef.current.style, { cursor: 'default' });
  }

  const menu = (
    <Menu>
      <Menu.Item>
        <a rel="noopener noreferrer" onClick={showAddTextboxDialog}>
          Markdown部件
        </a>
      </Menu.Item>
      <Menu.Item>
        <a rel="noopener noreferrer" onClick={showAddUrlDialog}>
          外部链接部件
        </a>
      </Menu.Item>
      <Menu.Item>
        <a rel="noopener noreferrer" onClick={showAddVideoDialog}>
          视频(&流)部件
        </a>
      </Menu.Item>
      <Menu.Divider />
      <Menu.Item>
        <a rel="noopener noreferrer" onClick={showAddContainerDialog}>
          报表容器部件
        </a>
      </Menu.Item>
      <Menu.Divider />
      <Menu.Item>
        <a rel="noopener noreferrer" onClick={showAddWeatherDialog}>
          天气预报
        </a>
      </Menu.Item>
    </Menu>
  )
  const onClose = () => {
    setIsVisible(false);
  };

  const handleVisibleChange = flag => {
    if (flag !== isVisible) setIsVisible(flag);
  };

  const showDrawer = () => {
    setIsVisible(true);
  };

  const handleSelectWidget = useCallback((widget_id) => {
    if (currentWidget.id !== widget_id) {
      const wid = find(dashboardOptions.dashboard.widgets, w => w.id === widget_id);
      setCurrentWidget(wid);
    }
  }, [currentWidget, dashboardOptions]);

  const handleSelectTemplate = (widget, dashboardOptions) => {
    const position = calculateNewWidgetPosition(dashboardOptions.dashboard.widgets, widget);
    const { AddTemplateWidget } = dashboardOptions;

    const url = `api/widgets/${widget.id}/fork/${dashboardOptions.dashboard.id}/${position.row}/${position.col}`;
    axios.get(url).then(wid => {
      let new_widget = new Widget(wid)
      AddTemplateWidget(new_widget);
      setCurrentWidget(new_widget);
      setIsVisible(true);
    });
  };

  useEffect(() => {
    const doTemplates = (widgets, dashboardOptions) => {
      const imageUrl = policy.getHost() + '/static/images/templates/';
      let results = [];

      let gs = toPairs(groupBy(widgets, 'template_type'));

      for (var i = 0; i < gs.length; i++) {
        let w = gs[i][1].length < 4 ? gs[i][1].length * 152 : 608;
        let c = (
          <Row type="flex" gutter={[8, 8]} style={{ width: w.toString() + 'px' }} >
            {map(gs[i][1], (widget) => (
              <Col span={6} flex="128px" >
                <button style={{ cursor: "crosshair" }} onClick={() => { handleSelectTemplate(widget, dashboardOptions) }}>
                  <img src={imageUrl + widget.template_logo} alt={widget.template_name} />
                  {widget.template_name}
                </button>
              </Col>
            ))}
          </Row>
        );
        results.push(c)
      }

      return results;
    }

    if (content.length === 0) {
      const url = "api/widgets";
      axios.get(url).then(widgets => {
        setContent(doTemplates(widgets, dashboardOptions));
      });
    }
  }, [dashboardOptions, content]);

  const node = currentWidget && (
    <>
      部件：
      <Select
        defaultValue={currentWidget.id}
        onChange={(value) => handleSelectWidget(value)}
        style={{ minWidth: 96 }}
      >
        {map(dashboardOptions.dashboard.widgets, (widget) => {
          if (!!widget.template_name) {
            return (
              <Select.Option key={widget.id} value={widget.id}>
                {widget.template_name + '[' + widget.id + ']'}
              </Select.Option>
            );
          }
        })}
      </Select>
    </>
  );

  return (
    <div ref={divRef} className={cx("add-widget-container", className)} {...props}
       onMouseMove={onMouseMove(draggable)}
       onMouseUp={onMouseUp(draggable)}
    >
      {currentWidget && (
        <Drawer
          title={node}
          placement="right"
          closable={true}
          onClose={onClose}
          destroyOnClose={true}
          onVisibleChange={handleVisibleChange}
          visible={isVisible}
          mask={false}
          bodyStyle={{ background: "#fff" }}
        >
          <TemplateDrawer dashboardOptions={dashboardOptions} currentWidget={currentWidget} />
        </Drawer>
      )}
      <div>
        <h2>
          <i className="zmdi zmdi-arrows" style={{ cursor: 's-resize' }}
            onMouseDown={onMouseDown(setDraggable)}
          />
        </h2>
        {content.length > 0 && (
          <Popover content={content[0]} title="指标卡" className="m-l-30">
            <Button type="primary">指标卡模板<DownOutlined /></Button>
          </Popover>
        )}
        {content.length > 1 && (
          <Popover content={content[1]} title="ECharts基础图表" className="m-l-15">
            <Button type="primary">ECharts基础模板<DownOutlined /></Button>
          </Popover>
        )}
        {content.length > 2 && (
          <Popover content={content[2]} title="其它模板" className="m-l-15">
            <Button type="primary">主题模板<DownOutlined /></Button>
          </Popover>
        )}
      </div>
      <div>
        <Dropdown overlay={menu} placement="topLeft" arrow>
          <Button className="m-r-15" data-test="AddTextboxButton">
            添加功能部件<DownOutlined />
          </Button>
        </Dropdown>
        <Button type="primary" className="m-r-30" onClick={showAddWidgetDialog}
          data-test="AddWidgetButton">
          添加视图部件
        </Button>
        {content.length > 0 && currentWidget && (
          <Button onClick={showDrawer}>
            <MenuFoldOutlined />
          </Button>
        )}
      </div>
    </div>
  );
}

AddWidgetContainer.propTypes = {
  dashboardOptions: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
  className: PropTypes.string,
};

export function DashboardComponent(props) {
  const dashboardOptions = useDashboard(props.dashboard, props.isContainer, props.isPop);
  const {
    dashboard,
    filters,
    setFilters,
    loadDashboard,
    loadWidget,
    removeWidget,
    saveDashboardLayout,
    globalParameters,
    refreshDashboard,
    refreshWidget,
    editingLayout,
    setGridDisabled,
  } = dashboardOptions;

  const [pageContainer, setPageContainer] = useState(null);
  const [bottomPanelStyles, setBottomPanelStyles] = useState({});

  const showPortal = window.location.search.includes("show_portal") && localStorage.getItem('colorstyle_mode');

  var hideHeader = window.location.search.includes("hide_header");
  var hideParametersUI = window.location.search.includes("hide_parameters");
  var isContainer = false;
  var widgetHidePopMenus = false;

  if (props.hide_header) hideHeader = props.hide_header;
  if (props.hide_parameters) hideParametersUI = props.hide_parameters;
  if (props.isContainer) isContainer = props.isContainer;
  if (props.widgetHidePopMenus) widgetHidePopMenus = props.widgetHidePopMenus;

  // 容器报表获取url参数值
  if (isContainer && globalParameters && globalParameters.length > 0) {
    var isChange = false;
    const queryParams = location.search;

    globalParameters.forEach(parameter => {
      const parameterValue = parameter.value;
      parameter.fromUrlParams(queryParams);

      if (Array.isArray(parameterValue) && Array.isArray(parameter.value)) {
        if (parameterValue.toString() !== parameter.value.toString()) {
          isChange = true;
        }
      }
      else if (parameterValue !== parameter.value) {
        isChange = true;
      }
    });

    if (isChange) {
      dashboardOptions.globalParameters = globalParameters;
      dashboard.widgets.map(wid => {
        if (wid.query && wid.query.$parameters) {
          wid.query.$parameters = null;  //视图不刷新问题
        }
        return wid;
      });
      //loadDashboard(true, globalParameters);
      refreshDashboard();
    }
  }
  var widgetMargins = -1;
  if (window.location.search.includes("widget_margins")) widgetMargins = parseInt(location.search.widget_margins);
  if (props.hasOwnProperty("widgetMargins")) widgetMargins = parseInt(props.widgetMargins);

  var widgetRadius = -1;
  if (window.location.search.includes("widget_radius")) widgetRadius = parseInt(location.search.widget_radius);
  if (props.hasOwnProperty("widgetRadius")) widgetRadius = parseInt(props.widgetRadius);

  const cssStyle = {
    left: 0,
    top: 0,
    width: '100%',
    height: '100%',
    position: 'fixed',
    background: '#00000000',
    zIndex: -1,
  }

  // const fireworksOptions = {
  //   speed: 3
  // }

  const particleRef = useRef();
  const bubblesRef = useRef();

  useEffect(() => {
    if (pageContainer) {
      if (showPortal && dashboard.layout.affect === "particle") {
        const pointSize = 3;
        new ParticleWave(particleRef.current, {
          uniforms: {
            size: { type: 'float', value: pointSize },
            field: { type: 'vec3', value: [0, 0, 0] },
            speed: { type: 'float', value: 7 }
          },
          onResize(w, h, dpi) {
            const position = []
            const color = []
            const width = 400 * (w / h)
            const depth = 300
            const height = 5
            const distance = 5
            for (let x = 0; x < width; x += distance) {
              for (let z = 0; z < depth; z += distance) {
                position.push(-width / 2 + x, -30, -depth / 2 + z)
                color.push(0, 1 - (x / width) * 1, 0.5 + x / width * 0.5, z / depth)
              }
            }
            if (this.uniforms) {
              this.uniforms.field = [width, height, depth]
              this.uniforms.size = (h / 400) * pointSize * dpi
            }
            if (this.buffers) {
              this.buffers.position = position
              this.buffers.color = color
            }
          }
        });
      }

      if (showPortal && dashboard.layout.affect === "bubbles") {
        const bubblesOptions = {
          quantity: 2,
          interval: 3,
          size: [5, 25],
          color: {
            R: [15, 240],
            G: [15, 240],
            B: [15, 240],
            A: 1
          }
        };
        if (bubblesRef.current) animate(bubblesRef.current, bubblesOptions);
      }

      const unobserve = resizeObserver(pageContainer, () => {
        if (editingLayout) {
          const style = window.getComputedStyle(pageContainer, null);
          const bounds = pageContainer.getBoundingClientRect();
          const paddingLeft = parseFloat(style.paddingLeft) || 0;
          const paddingRight = parseFloat(style.paddingRight) || 0;
          setBottomPanelStyles({
            left: Math.round(bounds.left) + paddingRight,
            width: pageContainer.clientWidth - paddingLeft - paddingRight,
            height: '60px',
          });
        }

        // reflow grid when container changes its size
        window.dispatchEvent(new Event("resize"));
      });
      return unobserve;
    }
  }, [pageContainer, editingLayout, showPortal, dashboard.layout.affect]);

  return isContainer ? (
    <div data-test={`DashboardId${dashboard.id}Container`}
      style={showPortal ? { width: "calc(100% - 30px)", marginLeft: "15px", marginTop: "0px", background: "#00000000" } : { width: "calc(100% - 30px)", marginLeft: "15px", marginTop: "0px" }}
    >
      {!hideParametersUI && !isEmpty(globalParameters) && (
        <div className={showPortal ? "dashboard-parameters m-b-10 p-5" : "dashboard-parameters m-b-10 p-5 bg-white tiled"}
          data-test="DashboardParameters"
          style={showPortal ? { background: "#00000000" } : null}
        >
          <Parameters parameters={globalParameters} onValuesChange={refreshDashboard} isContainer={isContainer} />
        </div>
      )}
      {!hideParametersUI && !isEmpty(filters) && (
        <div className={showPortal ? "m-b-10 p-5" : "m-b-10 p-5 bg-white tiled"}
          data-test="DashboardFilters"
          style={showPortal ? {
            background: "#00000000",
          } : null}
        >
          <Filters filters={filters} onChange={setFilters} />
        </div>
      )}
      <DashboardGrid
        dashboard={dashboard}
        dashboardOptions={dashboardOptions}
        widgets={dashboard.widgets}
        filters={filters}
        isEditing={editingLayout}
        isPublic={props.isPublic}
        onLayoutChange={editingLayout ? saveDashboardLayout : () => { }}
        onBreakpointChange={setGridDisabled}
        onLoadWidget={loadWidget}
        onRefreshWidget={refreshWidget}
        onRemoveWidget={removeWidget}
        onParameterMappingsChange={loadDashboard}
        widgetMargins={widgetMargins}
        widgetRadius={widgetRadius}
        widgetHidePopMenus={widgetHidePopMenus}
        isContainer={true}
        containerWidgetHeight={props.containerWidgetHeight}
        containerWidgetWidth={props.containerWidgetWidth}
      />
    </div>
  ) : (
    <React.Fragment>
      {showPortal && dashboard.layout.affect === "particle" && <div ref={particleRef} style={cssStyle} />}
      {/* {showPortal && dashboard.layout.affect === "fireworks" && <Fireworks options={fireworksOptions} style={cssStyle} />}       */}
      {showPortal && dashboard.layout.affect === "snowfall" && <Snowfall snowflakeCount={20} style={cssStyle} />}
      {showPortal && dashboard.layout.affect === "bubbles" && <canvas ref={bubblesRef} style={cssStyle} />}
      <div className="container" ref={setPageContainer} data-test={`DashboardId${dashboard.id}Container`}
        style={showPortal ? { background: "#00000000" } : (!!dashboard.layout.dashboardBg ? { background: dashboard.layout.dashboardBg } : null)}
      >
        {!hideHeader && (
          <DashboardHeader
            dashboardOptions={dashboardOptions}
            headerExtra={
              <DynamicComponent name="Dashboard.HeaderExtra" dashboard={dashboard} dashboardOptions={dashboardOptions} />
            }
            title={props.title}
            clickhandle={props.clickhandle}
            handleMenuClick={props.handleMenuClick}
          />
        )}
        {!hideParametersUI && !isEmpty(globalParameters) && (
          <div className={showPortal ? "dashboard-parameters m-b-10 p-5" : "dashboard-parameters m-b-10 p-5 bg-white tiled"}
            data-test="DashboardParameters"
            style={showPortal ? { background: "#00000000" } : null}
          >
            <Parameters parameters={globalParameters} onValuesChange={refreshDashboard} />
          </div>
        )}
        {!hideParametersUI && !isEmpty(filters) && (
          <div className={showPortal ? "m-b-10 p-5" : "m-b-10 p-5 bg-white tiled"}
            data-test="DashboardFilters"
            style={showPortal ? {
              background: "#00000000",
            } : null}
          >
            <Filters filters={filters} onChange={setFilters} />
          </div>
        )}
        {editingLayout && <DashboardSettings dashboardOptions={dashboardOptions} />}
        <div id="dashboard-container" style={(editingLayout || !showPortal) ? { marginLeft: '10px', marginRight: '10px', marginTop: '10px' } : hideHeader ? { marginTop: '15px' } : null}>
          <DashboardGrid
            dashboard={dashboard}
            dashboardOptions={dashboardOptions}
            widgets={dashboard.widgets}
            filters={filters}
            isEditing={editingLayout}
            isPublic={props.isPublic}
            onLayoutChange={editingLayout ? saveDashboardLayout : () => { }}
            onBreakpointChange={setGridDisabled}
            onLoadWidget={loadWidget}
            onRefreshWidget={refreshWidget}
            onRemoveWidget={removeWidget}
            onParameterMappingsChange={loadDashboard}
            widgetMargins={widgetMargins}
            widgetRadius={widgetRadius}
            widgetHidePopMenus={widgetHidePopMenus}
            isContainer={false}
          />
        </div>
        {editingLayout && <AddWidgetContainer dashboardOptions={dashboardOptions} style={bottomPanelStyles} />}
      </div>
    </React.Fragment>
  );
}

DashboardComponent.propTypes = {
  dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};

function DashboardPage({ dashboardSlug, dashboardId, onError }) {
  const [dashboard, setDashboard] = useState(null);
  const handleError = useImmutableCallback(onError);

  useEffect(() => {
    Dashboard.get({ id: dashboardId, slug: dashboardSlug })
      .then(dashboardData => {
        recordEvent("view", "dashboard", dashboardData.id);
        setDashboard(dashboardData);

        // if loaded by slug, update location url to use the id
        if (!dashboardId) {
          location.setPath(url.parse(dashboardData.url).pathname, true);
        }
      })
      .catch(handleError);
  }, [dashboardId, dashboardSlug, handleError]);

  return <div className="dashboard-page">{dashboard && <DashboardComponent dashboard={dashboard} />}</div>;
}

DashboardPage.propTypes = {
  dashboardSlug: PropTypes.string,
  dashboardId: PropTypes.string,
  onError: PropTypes.func,
};

DashboardPage.defaultProps = {
  dashboardSlug: null,
  dashboardId: null,
  onError: PropTypes.func,
};

// route kept for backward compatibility
routes.register(
  "Dashboards.LegacyViewOrEdit",
  routeWithUserSession({
    path: "/dashboard/:dashboardSlug",
    render: pageProps => <DashboardPage {...pageProps} />,
  })
);

routes.register(
  "Dashboards.ViewOrEdit",
  routeWithUserSession({
    path: "/dashboards/:dashboardId([^-]+)(-.*)?",
    render: pageProps => <DashboardPage {...pageProps} />,
  })
);
